package au.com.acpfg.misc.jemboss.settings;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.border.Border;
import org.knime.core.data.DataCell;
import org.knime.core.data.DataColumnSpec;
import org.knime.core.data.DataColumnSpecCreator;
import org.knime.core.data.DataTableSpec;
import org.knime.core.data.StringValue;
import org.knime.core.data.def.StringCell;
import org.knime.core.node.InvalidSettingsException;
import org.knime.core.node.NotConfigurableException;
import org.knime.core.node.util.ColumnFilter;
import org.knime.core.node.util.ColumnSelectionPanel;
import au.com.acpfg.misc.jemboss.local.AbstractTableMapper;
import au.com.acpfg.misc.jemboss.local.ProgramSettingsListener;
/**
* Implements a widget for sequence related data eg. ACD type sequence or seqall. Subclasses
* may provide an implementation for output of sequence data eg. outseq, outseqall and so on...
*
* @author andrew.cassin
*
*/
public class SequenceSetting extends StringSetting {
private static final int FASTA_LINE_LENGTH = 80; // how many chars per line in FASTA files
// persisted state
private boolean m_from_column = true;
private final JCheckBox m_ignore = new JCheckBox("ignore?");
public SequenceSetting(HashMap<String,String> attrs) {
super(attrs); // WILL save the value eg. column name
if (hasAttribute("from-column?")) {
m_from_column = new Boolean(attrs.get("from-column?")).booleanValue();
} else {
m_from_column = false;
}
if (hasAttribute("ignore?")) {
m_ignore.setSelected(new Boolean(getAttributeValue("ignore?")));
} else {
m_ignore.setSelected(false);
}
}
@Override
public boolean isInputFromColumn() {
if (m_ignore.isSelected())
return false;
return m_from_column;
}
@Override
public String getColumnName() {
if (isInputFromColumn())
return getValue();
return null;
}
/**
* Supports the <code>nullok</code> attribute where a setting may be omitted from the emboss invocation.
*
*/
@Override
public JComponent make_widget(DataTableSpec dt) {
String t = getType();
JPanel ret = new JPanel();
ret.setBorder(BorderFactory.createEmptyBorder());
ret.setLayout(new FlowLayout());
if (t.equals("sequence")) {
ColumnSelectionPanel csp = make_col_panel(dt);
m_from_column = true;
setValue(csp.getSelectedColumn());
csp.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
Object o = ((JComboBox)arg0.getSource()).getSelectedItem();
if (o instanceof DataColumnSpec) {
setValue(((DataColumnSpec)o).getName());
}
}
});
ret.add(csp);
} else {
ret.add(make_sequence_panel(dt));
}
boolean do_ignore = hasAttribute("nullok") && getAttributeValue("nullok").toLowerCase().startsWith("y");
if (do_ignore) {
ret.add(Box.createRigidArea(new Dimension(5,5)));
m_ignore.setSelected(true);
ret.add(m_ignore);
}
return ret;
}
/**
* Make a widget which permits the user to choose the KNIME column to take data from. The
* widget responds to default values and records the changes in the column to the specified ProgramSetting
* @param dt - tablespec which provides the initial list of suitable columns
* @return
*/
private ColumnSelectionPanel make_col_panel(DataTableSpec dt) {
final ArrayList<String> ok_cols = new ArrayList<String>();
ColumnSelectionPanel csp = new ColumnSelectionPanel((Border)null, new ColumnFilter() {
@Override
public boolean includeColumn(DataColumnSpec colSpec) {
boolean ok = (colSpec.getType().isCompatible(StringValue.class));
if (ok) {
//HACK BUG: reliant on column traversal order...
ok_cols.add(colSpec.getName());
}
return ok;
}
@Override
public String allFilteredMsg() {
return "ERROR: no suitable String columns available!";
}
}, false, false);
String default_value = getDefaultValue();
if (hasAttribute("value") && isInputFromColumn()) {
default_value = getValue();
}
try {
if (dt != null) {
csp.update(dt, "");
if (csp.getNrItemsInList() > 0) {
if (default_value.length() > 0) {
int idx = ok_cols.indexOf(default_value);
if (idx < 0)
idx = 0;
csp.setSelectedIndex(idx);
} else {
// last (ie. most recent) column is selected
csp.setSelectedIndex(csp.getNrItemsInList()-1);
}
}
}
} catch (NotConfigurableException nce) {
nce.printStackTrace();
}
return csp;
}
/**
* Returns a "sequence panel" which lets the user elect to provide data from a file (FASTA) or
* from an input column (KNIME table input). Provides all the logic to handle default values and
* updating of the setting as the user changes it.
*
* @param dt Input DataTableSpec to the node (used to display the required columns for the user)
* @return
*/
private JPanel make_sequence_panel(DataTableSpec dt) {
JPanel jp = new JPanel();
jp.setLayout(new BoxLayout(jp, BoxLayout.X_AXIS));
String where = isInput() ? "from" : "to";
final JRadioButton b1 = new JRadioButton(where+" file");
final JRadioButton b2 = new JRadioButton(where+" column");
final JButton open_file_button = new JButton(" Select File... ");
final ColumnSelectionPanel csp = make_col_panel(dt); // csp is always constructed but not always added (eg. output settings)
if (! m_from_column) {
File f = new File(getValue());
if (!f.exists() || !f.canRead()) {
Logger.getAnonymousLogger().warning("File '"+f.getName()+"' is not accessible anymore. Re-configure.");
} else {
open_file_button.setText(f.getName());
}
b1.setSelected(true);
b2.setSelected(false);
csp.setEnabled(false);
open_file_button.setEnabled(true);
} else {
b1.setSelected(false);
b2.setSelected(true);
csp.setEnabled(true);
open_file_button.setEnabled(false);
}
open_file_button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
JFileChooser fc = new JFileChooser();
int returnVal = fc.showSaveDialog(open_file_button);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File f = fc.getSelectedFile();
m_from_column = false;
setValue(f.getAbsolutePath());
open_file_button.setText(f.getName());
}
}
});
ActionListener al = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JRadioButton b = (JRadioButton) e.getSource();
boolean is_b1 = b.equals(b1); // file radio button?
m_from_column = !is_b1;
b1.setSelected(is_b1);
b2.setSelected(!is_b1);
if (is_b1) {
open_file_button.setEnabled(true);
if (csp != null)
csp.setEnabled(false);
} else {
if (csp != null)
csp.setEnabled(true);
open_file_button.setEnabled(false);
}
}
};
b1.addActionListener(al);
jp.add(b1);
jp.add(open_file_button);
b2.addActionListener(al);
jp.add(b2);
// csp is only added if the user must be given the choice ie. input setting
if (isInput()) {
csp.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
Object o = ((JComboBox)arg0.getSource()).getSelectedItem();
if (o instanceof DataColumnSpec) {
setValue(((DataColumnSpec)o).getName());
}
}
});
jp.add(csp);
}
return jp;
}
@Override
public void marshal(String id, DataCell c, PrintWriter fw)
throws IOException, InvalidSettingsException {
fw.println(">"+id);
if (c instanceof StringCell) {
StringCell sc = (StringCell) c;
char[] seq = sc.getStringValue().toCharArray();
for (int offset=0; offset < seq.length; offset += FASTA_LINE_LENGTH) {
int len = seq.length - offset;
if (len > FASTA_LINE_LENGTH)
len = FASTA_LINE_LENGTH;
char[] out = new char[len];
System.arraycopy(seq, offset, out, 0, len);
fw.println(out);
}
}
}
@Override
public void copy_attributes(HashMap<String,String> attrs) {
super.copy_attributes(attrs);
attrs.put("from-column?", new Boolean(m_from_column).toString());
attrs.put("ignore?", new Boolean(m_ignore.isSelected()).toString());
}
@Override
public void getArguments(ProgramSettingsListener l) throws InvalidSettingsException,IOException {
String t = getType();
// input-by-file specified but no file chosen?
String v = getValue();
if (!m_from_column && (v== null || v.length()<1))
throw new InvalidSettingsException("No file chosen for: "+getName());
// if ignore is chosen, it does not appear on the emboss command line... so...
if (m_ignore.isSelected())
return;
if (t.equals("sequence") || t.equals("seqall")) {
File f = File.createTempFile("infile", ".fasta");
l.addInputFileArgument(this, "-"+getName(), f);
} else if (t.equals("outseq") || t.equals("seqoutall") || t.equals("seqoutseq")) {
// HACK BUG: "cast" this to OutputFileSetting since it is the most appropriate now
OutputFileSetting ops = new OutputFileSetting(this.getAttributes());
ops.getArguments(l);
} else {
throw new InvalidSettingsException("Invalid argument type: "+t+" for "+getName());
}
}
public static boolean canEmboss(String acd_type) {
if (acd_type.equals("sequence") || acd_type.equals("seqall") ||
acd_type.equals("outseq") || acd_type.equals("seqoutall") ||
acd_type.equals("seqoutseq") || acd_type.equals("seqout")) {
return true;
}
return false;
}
}